package rpc.bootstrap.springboot;

import com.google.common.collect.Maps;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils;
import rpc.bootstrap.conf.BootProperties;
import rpc.bootstrap.spring.RefererBean;
import rpc.bootstrap.spring.RegistryBean;
import rpc.bootstrap.spring.ServiceBean;
import rpc.conf.RefererConfig;
import rpc.conf.RegistryConfig;
import rpc.conf.ServiceConfig;
import rpc.conf.WebConfig;
import rpc.core.FlowReq;
import rpc.core.FlowRes;
import rpc.core.plugin.FlowPrePlugin;
import rpc.core.plugin.FlowRetPlugin;
import rpc.core.plugin.HttpPrePlugin;
import rpc.core.plugin.HttpRetPlugin;
import rpc.core.plugin.Plugin;
import rpc.core.plugin.impl.proxy.ServiceProxy;
import rpc.log.LogFormatManager;
import rpc.log.LogFormatter;
import rpc.netty.http.HttpServer;
import rpc.netty.http.SelfCheckHttpServer;
import rpc.netty.http.WebRouteInfo;
import rpc.netty.tcp.NettyClient;
import rpc.netty.tcp.NettyServer;
import rpc.utils.ExceptionUtils;
import rpc.utils.IpUtils;
import rpc.utils.ReflectionUtils;

/*********
 *
 *
 *@author 197
 *@date 2020/6/19 19:05
 *
 **/
public class Bootstrap {

  private static final Logger logger = LoggerFactory.getLogger(Bootstrap.class);

  private final List<Plugin> pluginList = new ArrayList<>();
  private final List<ServiceBean> serviceBeanList = new ArrayList<>();
  private final List<NettyServer> nettyServerList = new ArrayList<>();
  private final List<SelfCheckHttpServer> selfCheckHttpServerList = new ArrayList<>();
  private final ApplicationContext context;
  private final List<NettyClient> nettyClientList = new ArrayList<>();

  private final Map<String, ServiceProxy> flowServiceProxyMap = new HashMap<>();
  private final Map<String, ServiceBean> serviceBeanMap = new HashMap<>();
  private final List<RegistryBean> registryBeanList = new ArrayList<>();
  private final Map<String, WebRouteInfo> webRouteMap = Maps.newHashMap();
  private final Environment environment;
  private final List<RefererBean> refererBeanList = new ArrayList<>();

  private final ExecutorService nettyClientExecutorService;
  private final BootProperties bootProperties;
  private HttpServer httpServer;
  private WebConfig webConfig = null;
  private List<FlowRetPlugin> flowRetPluginList;
  private List<FlowPrePlugin> flowPrePluginList;

  public Bootstrap(Environment environment, BootProperties bootProperties,
      ApplicationContext context) {
    this.environment = environment;
    this.bootProperties = bootProperties;
    this.context = context;

    // TODO 线程数 可配置
    nettyClientExecutorService = new ThreadPoolExecutor(32, 32, 0, TimeUnit.MICROSECONDS
        , new ArrayBlockingQueue<Runnable>(10000), new ThreadFactoryBuilder().setNameFormat("rpc-netty-client-work-%d").build());

    // 日志格式化
    ServiceLoader<LogFormatter> logFormatters = ServiceLoader.load(LogFormatter.class);
    String logFormat = environment.getProperty("rpc.log.format");
    if (StringUtils.isNotBlank(logFormat)) {
      logFormatters.forEach(c -> {
        if (c.getClass().getName().contains(logFormat)) {
          LogFormatManager.setLogFormatter(c);
        }
      });
    }
    LogFormatManager.init();
    // 添加 服务
    ServiceConfig serviceConfig = bootProperties.getService();
    List<ServiceConfig> services = Optional.ofNullable(bootProperties.getServices()).orElse(new ArrayList<>());
    Optional.ofNullable(serviceConfig).ifPresent(services::add);
    this.setServiceBeanList(services);
    //添加 引用
    List<RefererConfig> refererConfigList = Optional.ofNullable(bootProperties.getReferers()).orElse(new ArrayList<>());
    refererConfigList.add(bootProperties.getReferer());
    this.setRefererBean(refererConfigList);

    this.setRegistryConfig(bootProperties.getRegistry());
    this.setWebConfig(bootProperties.getWeb());
  }


  public Bootstrap addPlugin(Plugin plugin) {
    this.pluginList.add(plugin);
    return this;
  }

  public Bootstrap setRegistryConfig(RegistryConfig registryConfig) {
    if (Objects.nonNull(registryConfig)) {
      RegistryBean registryBean = new RegistryBean();
      BeanUtils.copyProperties(registryConfig, registryBean);
      this.registryBeanList.add(registryBean);
    } else {
      throw new RuntimeException("registry config not find");
    }
    return this;
  }

  public void setRefererBean(List<RefererConfig> referers) {

    Optional.ofNullable(referers).orElse(new ArrayList<>()).stream().filter(Objects::nonNull)
        .forEach(referer -> {
          RefererBean refererBean = new RefererBean();
          BeanUtils.copyProperties(referer, refererBean);
          refererBeanList.add(refererBean);
        });
  }

  public Bootstrap setWebConfig(WebConfig webConfig) {
    this.webConfig = webConfig;
    return this;
  }

  public Map<String, ServiceBean> getServiceBeanMap() {
    return serviceBeanMap;
  }

  public List<ServiceBean> getServiceBeanList() {
    return serviceBeanList;
  }

  public void setServiceBeanList(List<ServiceConfig> serviceConfigList) {
    Optional.ofNullable(serviceConfigList).orElse(new ArrayList<>()).stream()
        .filter(Objects::nonNull).forEach(serviceConfig -> {
      ServiceBean serviceBean = new ServiceBean();
      BeanUtils.copyProperties(serviceConfig, serviceBean);
      serviceBeanList.add(serviceBean);
    });
  }

  public List<SelfCheckHttpServer> getSelfCheckHttpServerList() {
    return selfCheckHttpServerList;
  }

  public List<NettyServer> getNettyServerList() {
    return nettyServerList;
  }


  public List<RefererBean> getRefererBeanList() {
    return refererBeanList;
  }

  public List<NettyClient> getNettyClientList() {
    return nettyClientList;
  }

  public ExecutorService getNettyClientExecutorService() {
    return nettyClientExecutorService;
  }

  public Map<String, WebRouteInfo> getWebRouteMap() {
    return webRouteMap;
  }

  public HttpServer getHttpServer() {
    return httpServer;
  }

  public void postBuild() {

    //  创建  service
//    registryBeanList

    serviceBeanList.stream().filter(Objects::nonNull).forEach(serviceBean -> {
      serviceBean.init();
      //初始化
      serviceBeanMap.put(String.valueOf(ReflectionUtils.getFliedIntValue(serviceBean.getInterfaceName(), "serviceId")), serviceBean);
      //netty
      nettyServerList.add(new NettyServer().setEnvironment(environment).setPort(serviceBean.getPort()));
      selfCheckHttpServerList.add(new SelfCheckHttpServer().setPort(serviceBean.getCheckPort()));
      if (Objects.nonNull(webConfig) && webConfig.isAutoWeb()) {
        String className = serviceBean.getInterfaceClass().getSimpleName().toLowerCase();
        serviceBean.getMethodMap().forEach((k, v) -> {
          String path = (StringUtils.isBlank(webConfig.getBasePath()) ? "" : webConfig.getBasePath()) + "/" + serviceBean.getInterfaceClass().getSimpleName() + "/" + v.getName();
          path = path.toLowerCase();
          String flowName = className + "." + v.getName().toLowerCase();
          WebRouteInfo webRouteInfo = new WebRouteInfo().setServiceProxy(this.flowServiceProxyMap.get(flowName)).setPath(path);
          webRouteMap.put(path, webRouteInfo);
          logger.info("add path: http://{}:{}{} ,{}", IpUtils.localIp(), webConfig.getPort(), path, this);
        });
      }
    });
    if (!CollectionUtils.isEmpty(webRouteMap)) {
      List<HttpPrePlugin> httpPrePluginList = Arrays.stream(environment.getProperty("rpc.http.pre.plugin", "")
          .split(",")).filter(StringUtils::isNotBlank).map(context::getBean).map(b -> (HttpPrePlugin) b).collect(Collectors.toList());
      List<HttpRetPlugin> httpRetPluginList = Arrays.stream(environment.getProperty("rpc.http.ret.plugin", "")
          .split(",")).filter(StringUtils::isNotBlank).map(context::getBean).map(b -> (HttpRetPlugin) b).collect(Collectors.toList());
      new ArrayList<>(context.getBeansOfType(HttpRetPlugin.class).values());
      this.httpServer = new HttpServer().setPort(this.webConfig.getPort()).setEnvironment(environment)
          .setHttpPrePluginList(httpPrePluginList).setHttpRetPluginList(httpRetPluginList)
      ;
    }

    // 创建 referer
    refererBeanList.forEach(refererBean -> refererBean.init());


  }

  public List<Plugin> getPluginList() {
    return pluginList;
  }

  public Bootstrap addServiceProxy(Map<String, ServiceProxy> flowServiceProxyMap) {
    this.flowServiceProxyMap.putAll(flowServiceProxyMap);
    return this;
  }

//  public Map<String, ServiceProxy> getFlowServiceProxyMap() {
//    return flowServiceProxyMap;
//  }

  public List<RegistryBean> getRegistryBeanList() {
    return this.registryBeanList;
  }


  public CompletableFuture<FlowRes> invokeFlow(String flowName, FlowReq req) {

    ServiceProxy serviceProxy = this.flowServiceProxyMap.get(flowName);
    if (Objects.nonNull(serviceProxy)) {
      try {
        FlowRes value = serviceProxy.invoke(req).get(req.getTimeOut(), TimeUnit.MILLISECONDS);
        return CompletableFuture.completedFuture(value);
      } catch (InterruptedException | ExecutionException e) {
        logger.error("exception:{} stackTrace:{}", e.getMessage(), ExceptionUtils.getTrace(e));
        return CompletableFuture.completedFuture(new FlowRes().fail(-824));
      } catch (TimeoutException e) {
        logger.error("exception:{} stackTrace:{}", e.getMessage(), ExceptionUtils.getTrace(e));
        return CompletableFuture.completedFuture(new FlowRes().fail(-818));
      }
    } else {
      CompletableFuture<FlowRes> future = new CompletableFuture<>();
      future.complete(new FlowRes().fail(-827).msg("flowName:" + flowName + ",no impl"));
      return future;
    }

  }
}
